package s7

import (
	"fmt"
	"giot/pkg/util"
	"strconv"
	"strings"
	"time"
)
import "github.com/robinson/gos7"

type Client struct {
	ipAddr  string
	port    int
	rack    int
	slot    int
	handler *gos7.TCPClientHandler
	client  gos7.Client
	helper  gos7.Helper
}

func (s7 *Client) Open() error {

	handler := gos7.NewTCPClientHandler(s7.ipAddr, s7.rack, s7.slot)
	//连接及读取超时
	handler.Timeout = 200 * time.Second
	//关闭连接超时
	handler.IdleTimeout = 200 * time.Second
	//打开连接
	err := handler.Connect()
	if err != nil {
		return err
	}
	s7.handler = handler
	//获取PLC对象
	s7.client = gos7.NewClient(s7.handler)
	return nil
}

func (s7 *Client) Close() {
	if s7.handler != nil {
		_ = s7.handler.Close()
	}
}

func (s7 *Client) ReadBool(point string) (bool, error) {
	//db100.0.1
	db, start, location, err2 := s7.getPoint(point)
	if nil != err2 {
		return false, err2
	}
	if location < 0 || location > 7 {
		return false, fmt.Errorf("点位错误,点位不能小于0和大于7")
	}
	data := make([]byte, 1)
	err := s7.client.AGReadDB(db, start, 1, data)
	if err != nil {
		return false, err
	}
	at := s7.helper.GetBoolAt(data[0], uint(location))
	return at, nil
}

func (s7 *Client) ReadInt16(p string) (int16, error) {
	var v int16
	err := s7.read(p, &v, 2)
	return v, err
}
func (s7 *Client) ReadUint16(p string) (uint16, error) {
	var r uint16
	err := s7.read(p, &r, 2)
	return r, err
}

func (s7 *Client) ReadInt32(p string) (int32, error) {
	var r int32
	err := s7.read(p, &r, 4)
	return r, err
}

func (s7 *Client) ReadUint32(p string) (uint32, error) {
	var r uint32
	err := s7.read(p, &r, 4)
	return r, err
}

func (s7 *Client) ReadInt8(p string) (int8, error) {
	var r int8
	err := s7.read(p, &r, 1)
	return r, err
}

func (s7 *Client) ReadUint8(p string) (uint8, error) {
	var r uint8
	err := s7.read(p, &r, 1)
	return r, err
}

func (s7 *Client) ReadFloat32(p string) (float32, error) {
	var r float32
	err := s7.read(p, &r, 4)
	return r, err
}

func (s7 *Client) ReadBytes(p string, count int) ([]byte, error) {
	data := make([]byte, count)
	err := s7.read(p, &data, count)
	return data, err
}

func (s7 *Client) ReadString(p string, size int) (string, error) {
	data := make([]byte, size)
	err := s7.read(p, &data, size)
	if err != nil {
		return "", err
	}
	at := s7.helper.GetStringAt(data, 0)
	return at, err
}

func (s7 *Client) WriteBool(p string, v bool) error {
	_, _, location, err := s7.getPoint(p)
	if err != nil {
		return err
	}
	data := make([]byte, 1)
	s7.helper.SetBoolAt(data[0], uint(location), v)
	return s7.write(p, data)
}

func (s7 *Client) WriteUint8(p string, v uint8) error {
	data := make([]byte, 1)
	s7.helper.SetValueAt(data, 0, v)
	err := s7.write(p, data)
	return err
}

func (s7 *Client) WriteInt8(p string, v int8) error {
	return s7.WriteUint8(p, uint8(v))
}

func (s7 *Client) WriteInt16(p string, v int16) error {
	return s7.WriteUint16(p, uint16(v))
}

func (s7 *Client) WriteUint16(p string, v uint16) error {
	data := make([]byte, 2)
	s7.helper.SetValueAt(data, 0, v)
	err := s7.write(p, data)
	return err
}

func (s7 *Client) WriteInt32(p string, v int32) error {
	return s7.WriteUint32(p, uint32(v))
}

func (s7 *Client) WriteUint32(p string, v uint32) error {
	data := make([]byte, 4)
	s7.helper.SetValueAt(data, 0, v)
	err := s7.write(p, data)
	return err
}

func (s7 *Client) WriteFloat(p string, v float32) error {
	data := make([]byte, 4)
	s7.helper.SetValueAt(data, 0, v)
	return s7.write(p, data)
}

func (s7 *Client) WriteBytes(p string, data []byte) error {
	return s7.write(p, data)
}

func (s7 *Client) read(p string, t interface{}, size int) error {
	db, start, _, err2 := s7.getPoint(p)
	if nil != err2 {
		return err2
	}
	status, err := s7.client.PLCGetStatus()
	if status == 8 {
		data := make([]byte, size)
		err := s7.client.AGReadDB(db, start, size, data)
		if err != nil {
			return err
		}
		s7.helper.GetValueAt(data, 0, t)
		return nil
	}
	return err
}

// WriteString 写入到DB表,不支持双字节的字符,例如:中文字符,中文写入会报错
func (s7 *Client) WriteString(p string, v string) error {
	data := make([]byte, len(v)+2)
	at := s7.helper.SetStringAt(data, 0, len(v), v)
	err := s7.write(p, at)
	return err
}

// ReadWString 读取双字节字符串
func (s7 *Client) ReadWString(p string, size int) (string, error) {
	buff := make([]byte, size)
	err := s7.read(p, buff, size)
	if err != nil {
		return "", err
	}
	var l, realSize int16
	s7.helper.GetValueAt(buff, 2, &l)
	realSize = int16((len(buff) - 4) / 2)
	if int(l)*2 > int(realSize) {
		l = realSize
		s7.helper.SetValueAt(buff, 2, l)
	}
	resultStr := s7.helper.GetWStringAt(buff, 0)
	return resultStr, err
}

// WriteWString 写入字符串,支持中文
func (s7 *Client) WriteWString(p string, value string) error {
	count := util.StrCount(value)
	data := make([]byte, count*2+4) //双字节编码,一个字占用两个字节,头部4个字节用于代表长度,字符个数,实际大小字符数的两倍+4
	resultData := s7.helper.SetWStringAt(data, 0, count, value)
	err := s7.write(p, resultData)
	return err
}

func (s7 *Client) write(p string, data []byte) error {
	db, start, _, err := s7.getPoint(p)
	if err != nil {
		return err
	}
	status, err := s7.client.PLCGetStatus()
	if status == 8 {
		err = s7.client.AGWriteDB(db, start, len(data), data)
		return err
	}
	return err
}

func (s7 *Client) getPoint(point string) (db int, start int, location int, e error) {
	p := point[2:]
	split := strings.Split(p, ".")

	if len(split) >= 1 {
		db, e = str2int(split[0])
	}
	if len(split) >= 2 && e == nil {
		start, e = str2int(split[1])
	}
	if len(split) >= 3 && e == nil {
		location, e = str2int(split[2])
	}
	return db, start, location, nil
}

func str2int(v string) (int, error) {
	i, e := strconv.Atoi(v)
	return i, e
}
